# -*- coding: utf-8 -*-
# @Time    : 2024/7/19 10:26
# @Author  : yujiahao
# @File    : 45_fastapi_backend_tasks.py
# @description:后台任务


"""
可以定义在返回响应后运行的后台任务。

这对需要在请求之后执行的操作很有用，但客户端不必在接收响应之前等待操作完成。

包括这些例子：

1. 执行操作后发送的电子邮件通知：
   - 由于连接到电子邮件服务器并发送电子邮件往往很“慢”（几秒钟），
     您可以立即返回响应并在后台发送电子邮件通知。

2. 处理数据：
   - 例如，假设您收到的文件必须经过一个缓慢的过程，
     您可以返回一个"Accepted"(HTTP 202)响应并在后台处理它。

使用 `BackgroundTasks` 的步骤：

1. 定义后台任务函数：
   - 首先定义一个普通的 Python 函数，这个函数将包含你希望在后台执行的逻辑。

2. 在路径操作中使用 `BackgroundTasks`：
   - 在你的路径操作函数中，添加一个 `BackgroundTasks` 参数。

3. 添加任务到后台任务中：
   - 使用 `background_tasks.add_task` 方法将你的后台任务函数和所需参数添加进去。

"""
from typing import Annotated

# todo 0、导入依赖BackgroundTasks
from fastapi import BackgroundTasks, FastAPI, Depends

app = FastAPI()

output_path = '../../../output/fastapi_test.txt'


# todo 1、创建一个任务函数
def write_notification(email: str, message=""):
    """
    创建要作为后台任务运行的函数。

    它只是一个可以接收参数的标准函数。

    它可以是 async def 或普通的 def 函数，FastAPI 知道如何正确处理。

    在这种情况下，任务函数将写入一个文件（模拟发送电子邮件）。

    由于写操作不使用 async 和 await，我们用普通的 def 定义函数

    """
    with open(output_path, mode="w") as email_file:
        content = f"notification for {email}: {message}\n"
        email_file.write(content)


@app.post("/send-notification/{email}")
# todo 2、使用 BackgroundTasks 类型声明一个参数
# FastAPI 会创建一个 BackgroundTasks 类型的对象并作为该参数传入。
async def send_notification(email: str, background_tasks: BackgroundTasks):
    """
    todo 添加后台任务
    用 .add_task() 方法将任务函数传到 后台任务 对象中
     - 在后台运行的任务函数(write_notification)。
     - 应按顺序传递给任务函数的任意参数序列(email)。
     - 应传递给任务函数的任意关键字参数(message="some notification")。
    """
    background_tasks.add_task(write_notification, email, message="some notification")
    return {"message": "Notification sent in the background"}


# todo 依赖注入
# 使用 BackgroundTasks 也适用于依赖注入系统，你可以在多个级别声明 BackgroundTasks 类型的参数：在 路径操作函数 里，在依赖中(可依赖)，在子依赖中，等等。
# FastAPI 知道在每种情况下该做什么以及如何复用同一对象，因此所有后台任务被合并在一起并且随后在后台运行
def write_log(message: str):
    with open(output_path, mode="a") as log:
        log.write(message)


def get_query(background_tasks: BackgroundTasks, q: str | None = None):
    if q:
        message = f"found query: {q}\n"
        background_tasks.add_task(write_log, message)
    return q


# 该示例中，信息会在响应发出 之后 被写到 fastapi_test.txt 文件。
# 如果请求中有查询，它将在后台任务中写入日志。
# 然后另一个在 路径操作函数 生成的后台任务会使用路径参数 email 写入一条信息。
@app.post("/send-notification_1/{email}")
async def send_notification_1(
        email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)]
):
    message = f"message to {email}\n"
    background_tasks.add_task(write_log, message)
    return {"message": "Message sent"}


"""
【技术细节】
BackgroundTasks 类直接来自 starlette.background。

它被直接导入/包含到 FastAPI 以便你可以从 fastapi 导入，并避免意外从 starlette.background 导入备用的 BackgroundTask (后面没有 s)。

通过仅使用 BackgroundTasks (而不是 BackgroundTask)，使得能将它作为路径操作函数的参数，并让 FastAPI 处理其余部分，就像直接使用 Request 对象。

在 FastAPI 中仍然可以单独使用 BackgroundTask，但必须在代码中创建对象，并返回包含它的 Starlette Response。



【另外要注意】
如果需要执行繁重的后台计算，并且不一定需要由同一进程运行（例如，您不需要共享内存、变量等），那么使用其他更大的工具（如 Celery）可能更好。https://docs.celeryq.dev/en/stable/

它们需要更复杂的配置，即消息/作业队列管理器，如 RabbitMQ 或 Redis，但它们允许在多个进程中运行后台任务，甚至是在多个服务器中。

如果需要从同一个 FastAPI 应用程序访问变量和对象，或者需要执行小型后台任务（如发送电子邮件通知），只需使用 BackgroundTasks 即可。

"""
